//------------------------------------------------------------------------------
// Original code by Christian Graus, modified by Ste Brennan for DNAide. 
// http://www.codeproject.com/cs/media/displacementfilters.asp
//------------------------------------------------------------------------------
using System;
using System.Drawing;
using System.Drawing.Imaging;

namespace DNAide.Drawing
{
	public static partial class BitmapAide
	{
		internal static void OffsetFilterAbs(Bitmap bitmap, Point[,] offset)
		{
			Bitmap bSrc = (Bitmap)bitmap.Clone();

			// GDI+ still lies to us - the return format is BGR, NOT RGB.
			BitmapData bmData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
			BitmapData bmSrc = bSrc.LockBits(new Rectangle(0, 0, bSrc.Width, bSrc.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);

			int scanline = bmData.Stride;

			System.IntPtr Scan0 = bmData.Scan0;
			System.IntPtr SrcScan0 = bmSrc.Scan0;

			unsafe
			{
				byte* p = (byte*)(void*)Scan0;
				byte* pSrc = (byte*)(void*)SrcScan0;

				int nOffset = bmData.Stride - bitmap.Width * 3;
				int nWidth = bitmap.Width;
				int nHeight = bitmap.Height;

				int xOffset, yOffset;

				for (int y = 0; y < nHeight; ++y)
				{
					for (int x = 0; x < nWidth; ++x)
					{
						xOffset = offset[x, y].X;
						yOffset = offset[x, y].Y;

						if (yOffset >= 0 && yOffset < nHeight && xOffset >= 0 && xOffset < nWidth)
						{
							p[0] = pSrc[(yOffset * scanline) + (xOffset * 3)];
							p[1] = pSrc[(yOffset * scanline) + (xOffset * 3) + 1];
							p[2] = pSrc[(yOffset * scanline) + (xOffset * 3) + 2];
						}

						p += 3;
					}
					p += nOffset;
				}
			}

			bitmap.UnlockBits(bmData);
			bSrc.UnlockBits(bmSrc);
		}

		internal static void OffsetFilter(Bitmap bitmap, Point[,] offset)
		{
			Bitmap bSrc = (Bitmap)bitmap.Clone();

			// GDI+ still lies to us - the return format is BGR, NOT RGB.
			BitmapData bmData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
			BitmapData bmSrc = bSrc.LockBits(new Rectangle(0, 0, bSrc.Width, bSrc.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);

			int scanline = bmData.Stride;

			System.IntPtr Scan0 = bmData.Scan0;
			System.IntPtr SrcScan0 = bmSrc.Scan0;

			unsafe
			{
				byte* p = (byte*)(void*)Scan0;
				byte* pSrc = (byte*)(void*)SrcScan0;

				int nOffset = bmData.Stride - bitmap.Width * 3;
				int nWidth = bitmap.Width;
				int nHeight = bitmap.Height;

				int xOffset, yOffset;

				for (int y = 0; y < nHeight; ++y)
				{
					for (int x = 0; x < nWidth; ++x)
					{
						xOffset = offset[x, y].X;
						yOffset = offset[x, y].Y;

						if (y + yOffset >= 0 && y + yOffset < nHeight && x + xOffset >= 0 && x + xOffset < nWidth)
						{
							p[0] = pSrc[((y + yOffset) * scanline) + ((x + xOffset) * 3)];
							p[1] = pSrc[((y + yOffset) * scanline) + ((x + xOffset) * 3) + 1];
							p[2] = pSrc[((y + yOffset) * scanline) + ((x + xOffset) * 3) + 2];
						}

						p += 3;
					}
					p += nOffset;
				}
			}

			bitmap.UnlockBits(bmData);
			bSrc.UnlockBits(bmSrc);
		}

		internal static void OffsetFilterAntiAlias(Bitmap bitmap, FloatPoint[,] fp)
		{
			Bitmap bSrc = (Bitmap)bitmap.Clone();

			// GDI+ still lies to us - the return format is BGR, NOT RGB.
			BitmapData bmData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
			BitmapData bmSrc = bSrc.LockBits(new Rectangle(0, 0, bSrc.Width, bSrc.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);

			int scanline = bmData.Stride;

			System.IntPtr Scan0 = bmData.Scan0;
			System.IntPtr SrcScan0 = bmSrc.Scan0;

			unsafe
			{
				byte* p = (byte*)(void*)Scan0;
				byte* pSrc = (byte*)(void*)SrcScan0;

				int nOffset = bmData.Stride - bitmap.Width * 3;
				int nWidth = bitmap.Width;
				int nHeight = bitmap.Height;

				double xOffset, yOffset;

				double fraction_x, fraction_y, one_minus_x, one_minus_y;
				int ceil_x, ceil_y, floor_x, floor_y;
				Byte p1, p2;

				for (int y = 0; y < nHeight; ++y)
				{
					for (int x = 0; x < nWidth; ++x)
					{
						xOffset = fp[x, y].X;
						yOffset = fp[x, y].Y;

						// Setup

						floor_x = (int)Math.Floor(xOffset);
						floor_y = (int)Math.Floor(yOffset);
						ceil_x = floor_x + 1;
						ceil_y = floor_y + 1;
						fraction_x = xOffset - floor_x;
						fraction_y = yOffset - floor_y;
						one_minus_x = 1.0 - fraction_x;
						one_minus_y = 1.0 - fraction_y;

						if (floor_y >= 0 && ceil_y < nHeight && floor_x >= 0 && ceil_x < nWidth)
						{
							// Blue

							p1 = (Byte)(one_minus_x * (double)(pSrc[floor_y * scanline + floor_x * 3]) +
								fraction_x * (double)(pSrc[floor_y * scanline + ceil_x * 3]));

							p2 = (Byte)(one_minus_x * (double)(pSrc[ceil_y * scanline + floor_x * 3]) +
								fraction_x * (double)(pSrc[ceil_y * scanline + 3 * ceil_x]));

							p[x * 3 + y * scanline] = (Byte)(one_minus_y * (double)(p1) + fraction_y * (double)(p2));

							// Green

							p1 = (Byte)(one_minus_x * (double)(pSrc[floor_y * scanline + floor_x * 3 + 1]) +
								fraction_x * (double)(pSrc[floor_y * scanline + ceil_x * 3 + 1]));

							p2 = (Byte)(one_minus_x * (double)(pSrc[ceil_y * scanline + floor_x * 3 + 1]) +
								fraction_x * (double)(pSrc[ceil_y * scanline + 3 * ceil_x + 1]));

							p[x * 3 + y * scanline + 1] = (Byte)(one_minus_y * (double)(p1) + fraction_y * (double)(p2));

							// Red

							p1 = (Byte)(one_minus_x * (double)(pSrc[floor_y * scanline + floor_x * 3 + 2]) +
								fraction_x * (double)(pSrc[floor_y * scanline + ceil_x * 3 + 2]));

							p2 = (Byte)(one_minus_x * (double)(pSrc[ceil_y * scanline + floor_x * 3 + 2]) +
								fraction_x * (double)(pSrc[ceil_y * scanline + 3 * ceil_x + 2]));

							p[x * 3 + y * scanline + 2] = (Byte)(one_minus_y * (double)(p1) + fraction_y * (double)(p2));
						}
					}
				}
			}

			bitmap.UnlockBits(bmData);
			bSrc.UnlockBits(bmSrc);
		}

		
		/// <summary>
		/// Takes a number and moves each pixel by a random amount that is within the bounds of that number
		/// </summary>
		/// <param name="bitmap"></param>
		/// <param name="degree"></param>
		public static void RandomJitter(Bitmap bitmap, Int16 degree)
		{
			Point[,] ptRandJitter = new Point[bitmap.Width, bitmap.Height];

			int nWidth = bitmap.Width;
			int nHeight = bitmap.Height;

			int newX, newY;

			short nHalf = (short)Math.Floor(DNAide.Core.CastAide.AsDouble(degree) / 2);
			Random rnd = new Random();

			for (int x = 0; x < nWidth; ++x)
				for (int y = 0; y < nHeight; ++y)
				{
					newX = rnd.Next(degree) - nHalf;

					if (x + newX > 0 && x + newX < nWidth)
						ptRandJitter[x, y].X = newX;
					else
						ptRandJitter[x, y].X = 0;

					newY = rnd.Next(degree) - nHalf;

					if (y + newY > 0 && y + newY < nWidth)
						ptRandJitter[x, y].Y = newY;
					else
						ptRandJitter[x, y].Y = 0;
				}

			OffsetFilter(bitmap, ptRandJitter);
		}

		/// <summary>
		/// Starts in the middle, and moves around in a circle, increasing the radius as it also increases the degree of rotation
		/// </summary>
		/// <param name="bitmap"></param>
		/// <param name="degree">default degree to .05</param>
		/// <param name="smoothing"></param>
		public static void Swirl(Bitmap bitmap, Double degree, Boolean smoothing)
		{
			int nWidth = bitmap.Width;
			int nHeight = bitmap.Height;

			FloatPoint[,] fp = new FloatPoint[nWidth, nHeight];
			Point[,] pt = new Point[nWidth, nHeight];

			Point mid = new Point();
			mid.X = nWidth / 2;
			mid.Y = nHeight / 2;

			double theta, radius;
			double newX, newY;

			for (int x = 0; x < nWidth; ++x)
				for (int y = 0; y < nHeight; ++y)
				{
					int trueX = x - mid.X;
					int trueY = y - mid.Y;
					theta = Math.Atan2((trueY), (trueX));

					radius = Math.Sqrt(trueX * trueX + trueY * trueY);

					newX = mid.X + (radius * Math.Cos(theta + degree * radius));
					if (newX > 0 && newX < nWidth)
					{
						fp[x, y].X = newX;
						pt[x, y].X = (int)newX;
					}
					else
						fp[x, y].X = pt[x, y].X = x;

					newY = mid.Y + (radius * Math.Sin(theta + degree * radius));
					if (newY > 0 && newY < nHeight)
					{
						fp[x, y].Y = newY;
						pt[x, y].Y = (int)newY;
					}
					else
						fp[x, y].Y = pt[x, y].Y = y;
				}

			if (smoothing)
				OffsetFilterAbs(bitmap, pt);
			else
				OffsetFilterAntiAlias(bitmap, fp);
		}


		/// <summary>
		/// Effect of the image being wrapped around a ball
		/// </summary>
		/// <param name="bitmap"></param>
		/// <param name="smoothing"></param>
		public static void Sphere(Bitmap bitmap, Boolean smoothing)
		{
			int nWidth = bitmap.Width;
			int nHeight = bitmap.Height;

			FloatPoint[,] fp = new FloatPoint[nWidth, nHeight];
			Point[,] pt = new Point[nWidth, nHeight];

			Point mid = new Point();
			mid.X = nWidth / 2;
			mid.Y = nHeight / 2;

			double theta, radius;
			double newX, newY;

			for (int x = 0; x < nWidth; ++x)
				for (int y = 0; y < nHeight; ++y)
				{
					int trueX = x - mid.X;
					int trueY = y - mid.Y;
					theta = Math.Atan2((trueY), (trueX));

					radius = Math.Sqrt(trueX * trueX + trueY * trueY);

					double newRadius = radius * radius / (Math.Max(mid.X, mid.Y));

					newX = mid.X + (newRadius * Math.Cos(theta));

					if (newX > 0 && newX < nWidth)
					{
						fp[x, y].X = newX;
						pt[x, y].X = (int)newX;
					}
					else
					{
						fp[x, y].X = fp[x, y].Y = 0.0;
						pt[x, y].X = pt[x, y].Y = 0;
					}

					newY = mid.Y + (newRadius * Math.Sin(theta));

					if (newY > 0 && newY < nHeight && newX > 0 && newX < nWidth)
					{
						fp[x, y].Y = newY;
						pt[x, y].Y = (int)newY;
					}
					else
					{
						fp[x, y].X = fp[x, y].Y = 0.0;
						pt[x, y].X = pt[x, y].Y = 0;
					}
				}

			if (smoothing)
				OffsetFilterAntiAlias(bitmap, fp);
			else
				OffsetFilterAbs(bitmap, pt);
		}

		/// <summary>
		/// Causes the image the warp as it disappears in the distance
		/// </summary>
		/// <param name="bitmap"></param>
		/// <param name="factor"></param>
		/// <param name="smoothing"></param>
		public static void TimeWarp(Bitmap bitmap, Byte factor, Boolean smoothing)
		{
			int nWidth = bitmap.Width;
			int nHeight = bitmap.Height;

			FloatPoint[,] fp = new FloatPoint[nWidth, nHeight];
			Point[,] pt = new Point[nWidth, nHeight];

			Point mid = new Point();
			mid.X = nWidth / 2;
			mid.Y = nHeight / 2;

			double theta, radius;
			double newX, newY;

			for (int x = 0; x < nWidth; ++x)
				for (int y = 0; y < nHeight; ++y)
				{
					int trueX = x - mid.X;
					int trueY = y - mid.Y;
					theta = Math.Atan2((trueY), (trueX));

					radius = Math.Sqrt(trueX * trueX + trueY * trueY);

					double newRadius = Math.Sqrt(radius) * factor;

					newX = mid.X + (newRadius * Math.Cos(theta));
					if (newX > 0 && newX < nWidth)
					{
						fp[x, y].X = newX;
						pt[x, y].X = (int)newX;
					}
					else
					{
						fp[x, y].X = 0.0;
						pt[x, y].X = 0;
					}

					newY = mid.Y + (newRadius * Math.Sin(theta));
					if (newY > 0 && newY < nHeight)
					{
						fp[x, y].Y = newY;
						pt[x, y].Y = (int)newY;
					}
					else
					{
						fp[x, y].Y = 0.0;
						pt[x, y].Y = 0;
					}
				}

			if (smoothing)
				OffsetFilterAntiAlias(bitmap, fp);
			else
				OffsetFilterAbs(bitmap, pt);
		}


		/// <summary>
		/// Moire 
		/// </summary>
		/// <param name="bitmap"></param>
		/// <param name="degree"></param>
		public static void Moire(Bitmap bitmap, Double degree)
		{
			int nWidth = bitmap.Width;
			int nHeight = bitmap.Height;

			Point[,] pt = new Point[nWidth, nHeight];

			Point mid = new Point();
			mid.X = nWidth / 2;
			mid.Y = nHeight / 2;

			double theta, radius;
			int newX, newY;

			for (int x = 0; x < nWidth; ++x)
				for (int y = 0; y < nHeight; ++y)
				{
					int trueX = x - mid.X;
					int trueY = y - mid.Y;
					theta = Math.Atan2((trueX), (trueY));

					radius = Math.Sqrt(trueX * trueX + trueY * trueY);

					newX = (int)(radius * Math.Sin(theta + degree * radius));
					if (newX > 0 && newX < nWidth)
					{
						pt[x, y].X = (int)newX;
					}
					else
					{
						pt[x, y].X = 0;
					}

					newY = (int)(radius * Math.Sin(theta + degree * radius));
					if (newY > 0 && newY < nHeight)
					{
						pt[x, y].Y = (int)newY;
					}
					else
					{
						pt[x, y].Y = 0;
					}
				}

			OffsetFilterAbs(bitmap, pt);
		}


		/// <summary>
		/// Makes things appear to be underwater
		/// </summary>
		/// <param name="bitmap"></param>
		/// <param name="wave"></param>
		/// <param name="smoothing"></param>
		public static void Water(Bitmap bitmap, Int16 wave, Boolean smoothing)
		{
			int nWidth = bitmap.Width;
			int nHeight = bitmap.Height;

			FloatPoint[,] fp = new FloatPoint[nWidth, nHeight];
			Point[,] pt = new Point[nWidth, nHeight];

			Point mid = new Point();
			mid.X = nWidth / 2;
			mid.Y = nHeight / 2;

			double newX, newY;
			double xo, yo;

			for (int x = 0; x < nWidth; ++x)
				for (int y = 0; y < nHeight; ++y)
				{
					xo = ((double)wave * Math.Sin(2.0 * 3.1415 * (float)y / 128.0));
					yo = ((double)wave * Math.Cos(2.0 * 3.1415 * (float)x / 128.0));

					newX = (x + xo);
					newY = (y + yo);

					if (newX > 0 && newX < nWidth)
					{
						fp[x, y].X = newX;
						pt[x, y].X = (int)newX;
					}
					else
					{
						fp[x, y].X = 0.0;
						pt[x, y].X = 0;
					}


					if (newY > 0 && newY < nHeight)
					{
						fp[x, y].Y = newY;
						pt[x, y].Y = (int)newY;
					}
					else
					{
						fp[x, y].Y = 0.0;
						pt[x, y].Y = 0;
					}
				}

			if (smoothing)
				OffsetFilterAntiAlias(bitmap, fp);
			else
				OffsetFilterAbs(bitmap, pt);
		}


		/// <summary>
		/// Pixellate
		/// </summary>
		/// <param name="bitmap"></param>
		/// <param name="pixel"></param>
		/// <param name="grid"></param>
		public static void Pixellate(Bitmap bitmap, Int16 pixel, Boolean grid)
		{
			int nWidth = bitmap.Width;
			int nHeight = bitmap.Height;

			Point[,] pt = new Point[nWidth, nHeight];

			int newX, newY;

			for (int x = 0; x < nWidth; ++x)
				for (int y = 0; y < nHeight; ++y)
				{
					newX = pixel - x % pixel;

					if (grid && newX == pixel)
						pt[x, y].X = -x;
					else if (x + newX > 0 && x + newX < nWidth)
						pt[x, y].X = newX;
					else
						pt[x, y].X = 0;

					newY = pixel - y % pixel;

					if (grid && newY == pixel)
						pt[x, y].Y = -y;
					else if (y + newY > 0 && y + newY < nHeight)
						pt[x, y].Y = newY;
					else
						pt[x, y].Y = 0;
				}

			OffsetFilter(bitmap, pt);
		}

	}
}

